package com.ahearttogo.rpc.client;

import com.aharttogo.rpc.transport.TransportClient;
import com.ahearttogo.rpc.codec.Decoder;
import com.ahearttogo.rpc.codec.Encoder;
import com.ahearttogo.rpc.proto.Request;
import com.ahearttogo.rpc.proto.Response;
import com.ahearttogo.rpc.proto.ServiceDescriptor;
import lombok.extern.slf4j.Slf4j;
import sun.misc.IOUtils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 调用远程服务的代理类
 * @author xiejialin
 * @date 2020/2/17 7:21 PM
 */
@Slf4j
public class RemoteInvoker implements InvocationHandler {

    private Class clazz;

    private Encoder encoder;

    private Decoder decoder;

    private TransportSelector selector;

    RemoteInvoker(Class clazz, Encoder encoder, Decoder decoder,TransportSelector selector){
        this.encoder = encoder;
        this.decoder = decoder;
        this.selector =selector;
        this.clazz = clazz;
    }

    /**
     * 远程调用
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Request request = new Request();
        request.setService(ServiceDescriptor.from(clazz,method));
        request.setParameters(args);

        Response resp = invokeRemote(request);
        if(resp == null || resp.getCode() != 0){
            throw  new IllegalStateException("fail to invoke remote: " + resp);
        }
        return resp.getData();
    }

    private Response invokeRemote(Request request) {
        TransportClient client = null;
        Response response = null;
        try{
            client = selector.select();
            byte[] outBytes = encoder.encode(request);
            InputStream receive = client.write(new ByteArrayInputStream(outBytes));
            byte[] inBytes = IOUtils.readFully(receive,receive.available(),true);
            response = decoder.decode(inBytes,Response.class);
        } catch (IOException e) {
            log.error(e.getMessage(),e);
            response = new Response();
            response.setCode(1);
            response.setMsg("RpcClient got error:" + e.getClass() + ":" + e.getMessage());
        } finally {
            if(client != null){
                selector.release(client);
            }
        }
        return response;
    }
}
